「這段程式碼的測試,每次都要啟動整個系統、連上資料庫、跑完一整條請求鏈路才能測到,是不是測試工具沒選對?」
這個問題背後,十次有九次不是工具問題。昨天講的 DI 容器陷阱,本質上是「容器設定裡藏著介面看不到的邏輯」;今天要換一個更根本的角度:一段程式碼能不能在不啟動整個系統的情況下被獨立測試,本身就是一種架構品質的檢驗方法——測試邊界跟架構邊界,講的其實是同一件事的兩種說法。
一段業務邏輯要獨立測試,需要能夠在測試環境裡把它的依賴替換掉——資料庫存取要能換成假的、外部 API 呼叫要能換成 mock、時間相關的邏輯要能控制時鐘。如果一個模組怎麼樣都沒辦法脫離其他模組單獨測試,通常不是測試工具不夠力,而是這個模組的依賴沒有被收斂成介面,邊界本身就沒有劃乾淨。
這件事之所以是「代理指標」而不是巧合,是因為兩者的根本原因是同一個:架構邊界劃得好,代表依賴方向清楚、外部依賴都藏在介面後面——這正好也是讓測試能夠替換依賴、獨立執行的必要條件。反過來,如果一段程式碼直接依賴具體的資料庫連線、直接呼叫外部服務的 SDK、邏輯散落在好幾個模組裡互相呼叫,這段程式碼會同時很難測試、也很難維護——兩個症狀共用同一個病因。
這個系列從 Day 01 開始反覆講一件事:架構的角色不是規範「怎麼寫」,是限制「能改到哪裡」。 而「能不能改到哪裡」,最常用的驗證方法就是「寫一個測試、跑一次、看結果」——測試能不能快速可信地跑起來,直接反映了架構有沒有把改動範圍收斂到一個可控邊界。
如果架構邊界乾淨、模組可以獨立測試,AI 能在幾秒鐘內對一個改動寫出一個快速、可信的測試,架構劃定的邊界跟 AI 實際能驗證的範圍很容易對齊。但如果架構邊界模糊、一個改動要啟動整個系統才能測到,AI 面臨兩個選擇:要嘛花大量時間跑一個很慢的整合測試(拖慢每一次查證的速度,久了容易被跳過),要嘛乾脆不測、只靠讀程式碼判斷「應該沒問題」——後者正是架構邊界模糊時最危險的地方:明明沒有把握,卻因為驗證成本太高而放棄驗證。
用一組對照來看這個差異:
❌ 架構邊界模糊,測試邊界跟著模糊:
class OrderProcessor
{
public function process(Order $order): void
{
$conn = new PDO($dsn, $user, $pass); // 直接依賴具體連線
$client = new HttpClient(); // 直接依賴具體 HTTP 實作
// ... 業務邏輯散落在存取細節之間
}
}
→ 要測試這段業務邏輯,必須真的連資料庫、真的打 API,
AI 沒辦法快速寫出一個可信的測試,
只能選擇跑很慢的整合測試,或乾脆不測
✅ 架構邊界清楚,測試邊界跟著清楚:
class OrderProcessor
{
public function __construct(
private OrderRepository $orders,
private PaymentGatewayClient $gateway
) {}
public function process(Order $order): void
{
// 業務邏輯只依賴介面,不依賴具體連線/傳輸細節
}
}
→ 測試時把 OrderRepository、PaymentGatewayClient
換成假實作,幾毫秒內驗證業務邏輯本身,
AI 能快速產出可信的查證結果
架構邊界不是為了「看起來整齊」才存在,它直接決定了 AI(或任何人)能不能在合理時間內,對一個改動做出有把握的查證。 這也是為什麼「這段程式碼好不好測」值得被當成日常開發裡隨時可以檢查的訊號——一旦發現某段邏輯測起來特別痛苦,那通常不是測試的問題,是架構邊界該重新檢視了。
第二部(Day 8-16)從「AI 在沒有清楚模組邊界時怎麼不小心跨模組耦合」開始,講到 ADR 讓 AI 讀懂設計意圖、架構邊界跟過度設計的分界、DI 容器的兩面性,一路到今天的「可測試性即架構品質」。這幾天想傳達的核心想法,跟第一部立下的主題句是同一件事的不同層次:架構的角色不是規範怎麼寫,是限制能改到哪裡、能不能被安全驗證。
回想你手上系統裡有沒有一段「每次要測都很痛苦」的程式碼?那個痛苦感,你有沒有把它當成架構邊界該調整的訊號,還是只當成測試工具不好用的抱怨?
明天進入第三部:架構規則要寫成可執行的檢查(linter/static analysis),不能只是寫在文件裡沒人強制——一條沒有工具把關的規則,多久會被忘記。